/* @flow */

const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;
const simplePathRE =
  /^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?']|\[".*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*\s*$/;

// keyCode aliases
const keyCodes: { [key: string]: number | Array<number> } = {
  esc: 27,
  tab: 9,
  enter: 13,
  space: 32,
  up: 38,
  left: 37,
  right: 39,
  down: 40,
  delete: [8, 46],
};
/*Github:https://github.com/answershuto*/
// #4868: modifiers that prevent the execution of the listener
// need to explicitly return null so that we can determine whether to remove
// the listener for .once
const genGuard = (condition) => `if(${condition})return null;`;

const modifierCode: { [key: string]: string } = {
  stop: "$event.stopPropagation();",
  prevent: "$event.preventDefault();",
  self: genGuard(`$event.target !== $event.currentTarget`),
  ctrl: genGuard(`!$event.ctrlKey`),
  shift: genGuard(`!$event.shiftKey`),
  alt: genGuard(`!$event.altKey`),
  meta: genGuard(`!$event.metaKey`),
  left: genGuard(`'button' in $event && $event.button !== 0`),
  middle: genGuard(`'button' in $event && $event.button !== 1`),
  right: genGuard(`'button' in $event && $event.button !== 2`),
};

export function genHandlers(
  events: ASTElementHandlers,
  native: boolean,
  warn: Function
): string {
  let res = native ? "nativeOn:{" : "on:{";
  for (const name in events) {
    const handler = events[name];
    // #5330: warn click.right, since right clicks do not actually fire click events.
    if (
      process.env.NODE_ENV !== "production" &&
      name === "click" &&
      handler &&
      handler.modifiers &&
      handler.modifiers.right
    ) {
      warn(
        `Use "contextmenu" instead of "click.right" since right clicks ` +
          `do not actually fire "click" events.`
      );
    }
    res += `"${name}":${genHandler(name, handler)},`;
  }
  return res.slice(0, -1) + "}";
}

function genHandler(
  name: string,
  handler: ASTElementHandler | Array<ASTElementHandler>
): string {
  if (!handler) {
    return "function(){}";
  }

  if (Array.isArray(handler)) {
    return `[${handler.map((handler) => genHandler(name, handler)).join(",")}]`;
  }

  const isMethodPath = simplePathRE.test(handler.value);
  const isFunctionExpression = fnExpRE.test(handler.value);

  if (!handler.modifiers) {
    return isMethodPath || isFunctionExpression
      ? handler.value
      : `function($event){${handler.value}}`; // inline statement
  } else {
    let code = "";
    let genModifierCode = "";
    const keys = [];
    for (const key in handler.modifiers) {
      if (modifierCode[key]) {
        genModifierCode += modifierCode[key];
        // left/right
        if (keyCodes[key]) {
          keys.push(key);
        }
      } else {
        keys.push(key);
      }
    }
    if (keys.length) {
      code += genKeyFilter(keys);
    }
    // Make sure modifiers like prevent and stop get executed after key filtering
    if (genModifierCode) {
      code += genModifierCode;
    }
    const handlerCode = isMethodPath
      ? handler.value + "($event)"
      : isFunctionExpression
      ? `(${handler.value})($event)`
      : handler.value;
    return `function($event){${code}${handlerCode}}`;
  }
}

function genKeyFilter(keys: Array<string>): string {
  return `if(!('button' in $event)&&${keys
    .map(genFilterCode)
    .join("&&")})return null;`;
}

function genFilterCode(key: string): string {
  const keyVal = parseInt(key, 10);
  if (keyVal) {
    return `$event.keyCode!==${keyVal}`;
  }
  const alias = keyCodes[key];
  return `_k($event.keyCode,${JSON.stringify(key)}${
    alias ? "," + JSON.stringify(alias) : ""
  })`;
}
